// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
// All rights reserved.  Use of this class is limited.
// Please see the LICENSE for more information.

package net.atomarrow.upload;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpUtils;

import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.FileRenamePolicy;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.Part;

import net.atomarrow.bean.UploadFile;
import net.atomarrow.configs.Config;
import net.atomarrow.ioc.ActionContext;

public class MultipartRequest {
    protected Hashtable parameters = new Hashtable(); 
    private int maxPostSize;
    private String encoding;
    private FileRenamePolicy policy;
    private List<UploadFile> uploadFiles;
    private List<String> excludeSuffixs;
    private boolean firstParse=true;

    public MultipartRequest() {
        uploadFiles=new ArrayList<UploadFile>();
        excludeSuffixs=Config.getConfig().configUploadExcludeSuffixs();
        if(excludeSuffixs==null){
            excludeSuffixs=Collections.EMPTY_LIST;
        }
        for(int i=0;i<excludeSuffixs.size();i++){
            String excludeSuffix=excludeSuffixs.get(i);
            if(!excludeSuffix.startsWith(".")){
                excludeSuffixs.set(i, "."+excludeSuffix);
            }
        }
        this.maxPostSize = Config.getConfig().configUploadMaxSize();
        this.encoding = "UTF-8";
        this.policy = new RandomFileRenamePolicy();
    }

    /**
     * 解析request
     * 
     * @throws IOException
     */
    public void parseRequest(HttpServletRequest request) {
        if (request == null)
            throw new IllegalArgumentException("request cannot be null");

        MultipartParser parser = null;
        try {
            parser = new MultipartParser(request, maxPostSize, true, true,
                    encoding);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (request.getQueryString() != null) {
            // Let HttpUtils create a name->String[] structure
            Hashtable queryParameters = HttpUtils.parseQueryString(request
                    .getQueryString());
            // For our own use, name it a name->Vector structure
            Enumeration queryParameterNames = queryParameters.keys();
            while (queryParameterNames.hasMoreElements()) {
                Object paramName = queryParameterNames.nextElement();
                String[] values = (String[]) queryParameters.get(paramName);
                Vector newValues = new Vector();
                for (int i = 0; i < values.length; i++) {
                    newValues.add(values[i]);
                }
                parameters.put(paramName, newValues);
            }
        }
        String uploadPath = getDefaultPath();
        String saveDirectory=getSaveDirectory(Config.getConfig().configUploadPath(), request);
        File file = getFileDir(request,saveDirectory);
        Part part;
        try {
            while ((part = parser.readNextPart()) != null) {
                String name = part.getName();
                if (name == null) {
                    throw new IOException(
                            "Malformed input: parameter name missing (known Opera 7 bug)");
                }
                if (part.isParam()) {
                    // It's a parameter part, add it to the vector of values
                    ParamPart paramPart = (ParamPart) part;
                    String value = paramPart.getStringValue();
                    Vector existingValues = (Vector) parameters.get(name);
                    if (existingValues == null) {
                        existingValues = new Vector();
                        parameters.put(name, existingValues);
                    }
                    existingValues.addElement(value);
                } else if (part.isFile()) {
                    FilePart filePart = (FilePart) part;
                    String fileName = filePart.getFileName();
                    if (fileName != null) {
                        filePart.setRenamePolicy(policy); // null policy is OK
                        if(isSafeFile(filePart.getFileName())){
                            filePart.writeTo(file);
                            uploadFiles.add(new UploadFile(name, saveDirectory, uploadPath,
                                    filePart.getFileName(), fileName,
                                    filePart.getContentType()));
                        }
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String getUploadPath(String uploadPath) {
        if(uploadPath.startsWith("/")){
            if(uploadPath.length()==1){
                uploadPath="";
            }else{
                uploadPath=uploadPath.substring(1);
            }
        }
        return uploadPath;
    }

    private File getFileDir(HttpServletRequest request,String saveDirectory) {
        // Save the dir

        File dir = new File(saveDirectory);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new RuntimeException("Directory " + saveDirectory
                        + " not exists and can not create directory.");
            }
        }
        // Check saveDirectory is truly a directory
        if (!dir.isDirectory())
            throw new IllegalArgumentException("Not a directory: " + saveDirectory);

        // Check saveDirectory is writable
        if (!dir.canWrite())
            throw new IllegalArgumentException("Not writable: " + saveDirectory);
        return dir;
    }

    /**
     * 解析保存文件
     * 
     * @param saveDirectory
     * @throws IOException
     */
    public UploadFile parseFile(String name){
        return parseFile(name, getDefaultPath());
    }

    private String getDefaultPath() {
        return getUploadPath(Config.getConfig().configUploadPath());
    }
    /**
     * 解析保存文件
     * 
     * @param saveDirectory
     * @throws IOException
     */
    public UploadFile parseFile(String name, String uploadPath){
        parseFiles(uploadPath);
        for(UploadFile file:uploadFiles){
            if(file.getParameterName().equals(name)){
                return file;
            }
        }
        return null;
    }

    /**
     * 解析保存多文件
     * 
     * @param saveDirectory
     * @throws IOException
     */
    public List<UploadFile> parseFiles() {
        return parseFiles(getDefaultPath());
    }
    /**
     * 解析保存多文件
     * 
     * @param saveDirectory
     * @throws IOException
     */
    public List<UploadFile> parseFiles(String uploadPath) {
        if(!firstParse){
            return uploadFiles;
        }
        firstParse=false;
        String saveDirectory = getSaveDirectory(uploadPath, ActionContext
                .getContext().getRequest());
        if (uploadPath == null)
            throw new IllegalArgumentException("saveDirectory cannot be null");
        if (maxPostSize <= 0) {
            throw new IllegalArgumentException("maxPostSize must be positive");
        }
        File dir = new File(saveDirectory);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new RuntimeException("Directory " + saveDirectory
                        + " not exists and can not create directory.");
            }
        }
        if (!dir.isDirectory())
            throw new IllegalArgumentException("Not a directory: " + uploadPath);

        if (!dir.canWrite())
            throw new IllegalArgumentException("Not writable: " + uploadPath);
        File newFile;
        String newPath;
        for (UploadFile uploadFile : uploadFiles) {
            newPath=saveDirectory + File.separatorChar+uploadFile.getFileName();
            newFile = new File(newPath);
            uploadFile.getFile().renameTo(newFile);
            uploadFile.setUploadPath(uploadPath);
            uploadFile.setSaveDirectory(saveDirectory);
        }
        return uploadFiles;
    }

    /**
     * 得到保存位置绝对路径
     * @param uploadPath
     * @param request
     * @return
     */
    private String getSaveDirectory(String uploadPath,
            HttpServletRequest request) {
        return request.getServletContext().getRealPath("/") + uploadPath;
    }
    /**
     * 校验后缀是否合法
     * @param uploadFile
     * @return
     */
    private boolean isSafeFile(String fileName) {
        for(String excludeSuffix:excludeSuffixs){
            if (fileName.toLowerCase().endsWith(excludeSuffix)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the names of all the parameters as an Enumeration of Strings. It
     * returns an empty Enumeration if there are no parameters.
     * 
     * @return the names of all the parameters as an Enumeration of Strings.
     */
    public Enumeration getParameterNames() {
        return parameters.keys();
    }

    /**
     * Returns the value of the named parameter as a String, or null if the
     * parameter was not sent or was sent without a value. The value is
     * guaranteed to be in its normal, decoded form. If the parameter has
     * multiple values, only the last one is returned (for backward
     * compatibility). For parameters with multiple values, it's possible the
     * last "value" may be null.
     * 
     * @param name
     *            the parameter name.
     * @return the parameter value.
     */
    public String getParameter(String name) {
        try {
            Vector values = (Vector) parameters.get(name);
            if (values == null || values.size() == 0) {
                return null;
            }
            String value = (String) values.elementAt(values.size() - 1);
            return value;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Returns the values of the named parameter as a String array, or null if
     * the parameter was not sent. The array has one entry for each parameter
     * field sent. If any field was sent without a value that entry is stored in
     * the array as a null. The values are guaranteed to be in their normal,
     * decoded form. A single value is returned as a one-element array.
     * 
     * @param name
     *            the parameter name.
     * @return the parameter values.
     */
    public String[] getParameterValues(String name) {
        try {
            Vector values = (Vector) parameters.get(name);
            if (values == null || values.size() == 0) {
                return null;
            }
            String[] valuesArray = new String[values.size()];
            values.copyInto(valuesArray);
            return valuesArray;
        } catch (Exception e) {
            return null;
        }
    }
    public Map getParameterMap() {
        Map map = new HashMap();
        Enumeration enumm = getParameterNames();
        while (enumm.hasMoreElements()) {
            String name = (String) enumm.nextElement();
            map.put(name, getParameterValues(name));
        }
        return map;
    }

    public int getMaxPostSize() {
        return maxPostSize;
    }

    public void setMaxPostSize(int maxPostSize) {
        this.maxPostSize = maxPostSize;
    }

    public String getEncoding() {
        return encoding;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public FileRenamePolicy getPolicy() {
        return policy;
    }

    public void setPolicy(FileRenamePolicy policy) {
        this.policy = policy;
    }

}
